<?php
/*
Data tree manipulation


class dataTree
	bool load(& array array), loadData(& array array): load a data array for manipulation
	mixed get([string path], [string syntax], [bool indexOnKeys]) : returns path value
	bool set(string path, value, [string syntax]): set path value, create nodes if necessary
	bool setSyntax(string syntax): set default syntax
	string getSyntax(void): return default syntax
	bool setContext(string context, [string syntax]): set default context (for relative paths)
	string getContext([string syntax]): return default context
	int count(string path, [string syntax]) : return the count of nodes targeted by path
	bool isPathSet(string path, [string syntax]) : return wether there is one or more nodes targeted by path

class dataNode
	__constructor(string key)
	bool setValue(& value) : set node scalar value, if value is an array, create a node child for each element
	mixed getValue(void) : returns node scalar value, or an array of child nodes values, indexed on child keys
	bool setParent(& node): attach node one parent node (=references parent node)
	bool appendChild(& node, [bool forceOverWrite]): attach a node as child node. overwrite existing node with same key if forceOverWrite is true
	object getChild(string key): return a reference on specified child node, or NULL if don't exist such node
	array getChildren(void) : return child nodes array (indexed on nodes keys)
	string getKey(void): return node key

------------------------------------------------------------------------------------------ syntaxes

array(
	'nom' => 'toto',
	'collections'=>array(
		0 => array(
			'objet'=>'porte-clefs',
			'qte' => 12),
		1 => array(
			'objet' => 'capsules',
			'qte' => 57
		)
	)
);

brackets

	[nom] = 'toto'
	[collections][0][objet] = 'porte clefs'
	(avec pour contexte [collections][1]) ~[qte]= 57
	~key= 1
	~up[0]=('objet'=>'porte-clefs','qte' => 12)
	~upkey='collections'

slashes

	/nom='toto'
	/collections =  'porte clefs'
	(avec pour contexte /collections/1) qte= 57
	~key= 1
	~up/0=('objet'=>'porte-clefs','qte' => 12)
	~up/~key='collections'

file version 2005-06-22 12:19:18

2005-06-13 12:12:08
	changed setValue(& value) to setValue(value), strange behaviors happened due to reference use
	fixed bracketsToSlashes() incorrect handling of [up*] and [key*] pathes

2005-06-22 12:12:07
	added count()
	added isPathSet()

2005-06-27 11:21:10
	added a third arg indexOnKeys to get method; boolean default false. If true, result values will be indexed on their initial key within the result array

2005-06-28 10:42:33
	added mergeNodes arg to dataNode->setValue(), default true will merge new nodes with existing ones

2005-07-20 16:48:06
	added dataNode->getType()
	added dataNode->addValue()
	added dataTree->getPathType(path)
	added dataTree->add(path,value)

file version 2005-07-20 17:26:21
*/

class dataTree {
/*--------------------------------------------------------------------------------------------------
                                                                                         constructor
--------------------------------------------------------------------------------------------------*/
	function dataTree($syntax=NULL) {
		$this->setSyntax($syntax);
		$this->root=&new dataNode('root');
	}
/*--------------------------------------------------------------------------------------------------
                                                                                      public methods
--------------------------------------------------------------------------------------------------*/
	function load(& $array) {
		/*if (isset($this->root)) unset($this->root);
		$this->root=&new dataNode('root');*/
		return $this->root->setValue($array);
	}

	function loadData(& $array) {
		return $this->load($array);
	}

	function getContext($syntax=NULL) {
		$context=$this->context;
		if ($syntax=='brackets' or ($syntax===NULL and $this->syntax=='brackets'))
			$context=$this->slashesToBrackets($context);
		return $context;
	}

	function setContext($context,$syntax=NULL) {
		if (!$this->context=$this->fullSlashesPath($context,$syntax)) return false;
		else return true;
	}

	function getSyntax() {
		return $this->syntax;
	}

	function setSyntax($syntax) {
		switch ($syntax) {
		case 'slashes':
		case 'brackets':
			$this->syntax=$syntax;
			return true; break;
		default:
			return false; break;
		}
	}

	/*function count($path=NULL,$syntax=NULL) {
		if (!$path=$this->fullSlashesPath($path,$syntax)) {
			//trigger_error
			return NULL;
		}
		// getting targeted nodes array
		$targetedNodes=& $this->collectPath($this->root,$path);
		if (!is_array($targetedNodes)) // targeted nodes don't exist
			return NULL;
		return count($targetedNodes);
	}*/

	function getKeys($path) {
		if (!$path=$this->fullSlashesPath($path,$syntax)) {
			//trigger_error
			return NULL;
		}
		// getting targeted nodes array
		$targetedNodes=& $this->collectPath($this->root,$path);
		if (!is_array($targetedNodes) or count($targetedNodes)!=1) {
			// targeted node don't exist or there is more than one
			return NULL;
		}
		$target=current($targetedNodes);
		return $target->getChildrenNames();
	}/**/

	/*
	*/
	function & get($path=NULL, $syntax=NULL,$indexOnKeys=false) {
		//echo "<p>datatree: getting $path</p>";
		// getting all ?
		if ($path==NULL or $path=='' or $path=='/')
			return $this->root->getValue();
		// getting full slashes path
		if (!$path=$this->fullSlashesPath($path,$syntax)) {
			//trigger_error
			return NULL;
		}
		// echo "<p>datatree: getting $path</p>";
		// getting keys or values ?
		if (substr($path,-5,5)=='/~key') {
			$mode='keys';
			$path=substr($path,0,-5);// echo $path;
		} else {
			$mode='values';
		}
		//echo "<p>datatree: getting $path</p>";
		// getting targeted nodes array
		$targetedNodes=& $this->collectPath($this->root,$path);
		//echo "<p>datatree->get: ".count($targetedNodes)." nodes for ".$path."</p>";
		if (!is_array($targetedNodes)) {
			// targeted nodes don't exist
			$result=null;
			return $result;
		}
		$result=array();
		// replacing all nodes by either their value or their key
		$keys=array_keys($targetedNodes);
		switch ($mode) {
			case 'values':
				foreach ($keys as $key) {
					if ($indexOnKeys) $result[$key]=$targetedNodes[$key]->getValue();
					else $result[]=$targetedNodes[$key]->getValue();
				}
				break;
			case 'keys':
				foreach ($keys as $key)
					$result[]=$targetedNodes[$key]->getKey();
				break;
		}

		if (count($result)==1) $result=current($result);

		//echo "<p>dataTree->get: ".affvar($result,$path)."</p>";
		return $result;
	}

	/* returns a text translation of the tree
	*/
	function getAsText($path, $syntax=NULL) {
		return $this->root->getAsText();
	}

	function set($path, $value, $syntax=NULL) {
		if ($path=='/' or $path===NULL or $path=='') {
			return $this->root->setValue($value);
		}
		// getting full slashes path
		if (!$path=$this->fullSlashesPath($path,$syntax)) {
			//trigger_error
			return NULL;
		}
		// getting targeted nodes array
		$targetedNodes=& $this->collectPath($this->root,$path);
		if (!is_array($targetedNodes)) {
			// targeted nodes don't exist
			$targetedNodes=& $this->createPath($this->root, $path);
		}
		// replacing all nodes by either their value or their key
		$keys=array_keys($targetedNodes);
		foreach ($keys as $key) {
			if (!$targetedNodes[$key]->setValue($value)) return false;
		}
		return true;
	}

	/*
	*/
	function add($path,$value,$syntax=NULL) {
		if ($path=='/' or $path===NULL or $path=='') {
			return $this->root->addValue($value);
		}
		// getting full slashes path
		if (!$path=$this->fullSlashesPath($path,$syntax)) {
			//trigger_error
			return NULL;
		}
		// getting targeted nodes array
		$targetedNodes=& $this->collectPath($this->root,$path);
		if (!is_array($targetedNodes)) {
			// targeted nodes don't exist
			$targetedNodes=& $this->createPath($this->root, $path);
		}
		// replacing all nodes by either their value or their key
		$keys=array_keys($targetedNodes);
		foreach ($keys as $key) {
			if (!$targetedNodes[$key]->addValue($value)) return false;
		}
		return true;
	}

	function isPathSet($path, $syntax=NULL) {
		// getting full slashes path
		if (!$path=$this->fullSlashesPath($path,$syntax)) {
			//trigger_error
			return 0;
		}
		// getting targeted nodes array
		$targetedNodes=& $this->collectPath($this->root,$path);
		return is_array($targetedNodes);
	}

	/*
	*/
	function getPathType($path, $syntax=NULL) {
		// getting full slashes path
		if (!$path=$this->fullSlashesPath($path,$syntax)) {
			//trigger_error
			return 0;
		}
		// getting targeted nodes array
		$targetedNodes=& $this->collectPath($this->root,$path);
		if (count($targetedNodes)!=1) return false;
		$node=&current($targetedNodes);
		return $node->getType();
	}

	function count($path, $syntax=NULL) {
		// getting full slashes path
		if (!$path=$this->fullSlashesPath($path,$syntax)) {
			//trigger_error
			return 0;
		}
		// getting targeted nodes array
		$targetedNodes=& $this->collectPath($this->root,$path);
		if (!is_array($targetedNodes)) return 0;
		$count=0;
		$keys=array_keys($targetedNodes);
		foreach ($keys as $key)
			$count+=$targetedNodes[$key]->countLeaves();
		return $count;
	}

	function applyFunction($path,$function) {
		if (!function_exists($function)) {
			trigger_error("dataTree->applyFunction(string path, string function): function $function is not defined");
			return false;
		}
		// getting full slashes path
		if (!$path=$this->fullSlashesPath($path)) {
			trigger_error("dataTree->applyFunction(string path, string function): invalid path $path");
			return false;
		}
		// getting targeted nodes array
		$targetedNodes=& $this->collectPath($this->root,$path);
		if (!is_array($targetedNodes)) {
			// targeted nodes don't exist
			//$targetedNodes=& $this->createPath($this->root, $path);
			trigger_error("dataTree->applyFunction(string path, string function): path $path don't exist");
			return false;
		}
		//echo wakaffvar($this->root->getvalue(),"root before applying $function to $path");
		// applying function to nodes values
		$keys=array_keys($targetedNodes);
		foreach ($keys as $key) {
			$value=$targetedNodes[$key]->getValue();
			//echo affvar($value);
			$newvalue=$function($value);
			// echo "<p>datatree->applyfunction: $function($value)=$newvalue</p>";
			if (!$targetedNodes[$key]->setValue($newvalue)) return false;
			//echo "<p>applied $function($value) to ".$targetedNodes[$key]->getName()." => ".$targetedNodes[$key]->getValue()."</p>";
		}
		// echo wakaffvar($this->root->getvalue(),"root after applying $function to $path");
		return true;
	}

	function show() {
		echo "<pre>";
		print_r($this);
		echo "</pre>";
	}

	// retourne une expression r guli re permettant d'identifier un chemin correct
	function makePathEreg($syntax=NULL) {
		if ($syntax!="slashes" and $syntax!="brackets") $syntax=$this->syntax;
		switch ($syntax) {
		case "brackets":
			/*return "~?(\[".$this->parameters['ereg_key']."\]"
			.'((\['.$this->parameters['ereg_key'].'\])|[*]|up)*'
			.'(key)?)';*/
			return "~|" // chemin relatif seul ou
			."(~?\[".$this->ereg_key."\]" // chemin relatif optionnel suivi d'au moins une etape-clef
			.'((\['.$this->ereg_key.'\])|[*]|up)*' // puis de zero ou plus etapes-clefs, * ou up
			.')'
			.'(key)?'; // se terminant optionnellement par key
			break;
		case "slashes":
		default:
			/*return '(/|((/'.$this->parameters['ereg_key'].')|(/~[*]))*' // ne doit pas commencer par ~up
			.'((/'.$this->parameters['ereg_key'].')|(/~up))' // ne doit pas se terminer par /~*
			.'(/~key)?)';*/
			/*return '(/' // chemin vide
			.'|(/'.$this->parameters['ereg_key'].'' // au moins un chemin normal
			.'((/'.$this->parameters['ereg_key'].')|(/~up)|(/~[*]))*)'
			.'(/~key)?)';*/
			return '/' // chemin vide
			.'|((/'.$this->ereg_key.')|(/~[*]))' // au moins un chemin normal ou /~*
			.'(/'.$this->ereg_key.')|(/~up)|(/~[*]))*' // suivi d'un ou plusieurs chemins normaux ou ~up
			.'(/~key)?';
			break;
		}
	}
/*--------------------------------------------------------------------------------------------------
                                                                                          properties
--------------------------------------------------------------------------------------------------*/
	var $root;
	var $syntax='slashes';
	var $context='';
	var $ereg_key='[a-zA-Z0-9_@+*#|-]+';
	var $verbose=false;
/*--------------------------------------------------------------------------------------------------
                                                                                     private methods
--------------------------------------------------------------------------------------------------*/
	/* returns a array of references to nodes  pointed by path (slashes syntax) or NULL
	*/
	function & collectPath(& $node,$path) {
		$null=null; $false=false;
		if (!is_object($node) or get_class($node)!='datanode') return $null;
		//$result=array();
		// echo "<p>Retrieving ".affvar($path,'path')."</p>";
		switch (true) {
			case $path=="/*": // end of path, all children
				//echo "end of path, all children";

				// echo affvar($children,$node->getKey()." children");
				$children=& $node->getChildren();
				return $children;
				/*$childrenlist=$node->getChildrenNames();
				$found=array();
				foreach ($childrenlist as $key) {
					$found[$key]=& $node->getChild($key);
				}
				return $found;*/
				break;
			case ereg('^/\*(/.+)$',$path,$found): // intermediate level, joker, exploring all children
				$endPath=$found[1];
				//echo affvar($found);
				$children=& $node->getChildren();
				$keys=array_keys($children);
				//echo affvar($keys);
				$result=array();
				foreach ($keys as $key) {
					$child=& $children[$key];
					//echo affvar($child);
					//echo "<p>will get child $key children</p>";
					if ($endPath==$path) { trigger_error("$path = $endPath",E_USER_WARNING); return false;}
					$moreResult=& $this->collectPath($child,$endPath);
					if ($moreResult==NULL) return $null;
					//echo affvar($moreResult,'moreResult');
					for ($g=0; $g<count($moreResult); $g++)
						$result[]=& $moreResult[$g];/**/
					//$result=array_merge($result,$moreResult);
				}/**/
				return $result;
				break;
			case ereg('^/([^/]+)(/.+)$',$path,$found): // intermediate level, single road
				//echo affvar($found,'intermediate');
				$nextKey=$found[1]; $endPath=$found[2];
				$nextNode=& $node->getChild($nextKey);
				if (!is_object($nextNode) or get_class($nextNode)!='datanode') return $null;
				if ($endPath==$path) { trigger_error("$path = $endPath",E_USER_WARNING); return $false;}
				$r=$this->collectPath($nextNode, $endPath);
				return $r;
				break;
			case ereg('^/([^/]+)$',$path,$found): // end of path, single child
				$targetKey=$found[1];
				$targetNode=& $node->getChild($targetKey);
				//echo "<p>dataTree: single node targeted ".kapaffvar($targetNode,$targetKey)." from ".kapaffvar($node)."</p>";
				if (!is_object($targetNode) or get_class($targetNode)!='datanode') return $null;
				$result[]=& $targetNode;
				return $result;
				break;
			default: // unsupported path
				trigger_error("DataTree->collectPath: unsupported path $path");
				return $null;
		}
		// return $result;
	}

	/* create nodes targeted by path if they don't exist
	return array of created nodes references
	*/
	function createPath(& $node, $path) {
		switch (true) {
			case ereg('^/\*(/.+)$',$path,$found): // intermediate level, joker, exploring all children
				$endPath=$found[1];
				//echo affvar($found);
				$children=& $node->getChildren();
				$keys=array_keys($children);
				//echo affvar($keys);
				$result=array();
				foreach ($keys as $key) {
					$child=& $children[$key];
					//echo affvar($child);
					//echo "<p>will get child $key children</p>";
					$moreResult=& $this->createPath($child,$endPath);
					if ($moreResult==NULL) return NULL;
					//echo affvar($moreResult,'moreResult');
					for ($g=0; $g<count($moreResult); $g++)
						$result[]=& $moreResult[$g];/**/
					//$result=array_merge($result,$moreResult);
				}/**/
				return $result;
				break;
			case ereg('^/([^/]+)(/.+)$',$path,$found): // intermediate level, single road
				//echo affvar($found,'intermediate');
				$nextKey=$found[1]; $endPath=$found[2];
				$nextNode=& $node->getChild($nextKey);
				if (!is_object($nextNode) or get_class($nextNode)!='datanode') {
					// creating new node
					$nextNode=& new dataNode($nextKey);
					// attaching it on the tree
					$node->appendChild($nextNode);
					if ($this->verbose) echo "<p>growed node $nextKey on ".$node->getKey()."</p>";
				}
				return $this->createPath($nextNode, $endPath);
				break;
			case ereg('^/([^/]+)$',$path,$found): // end of path, single child
				$targetKey=$found[1];
				$targetNode=& $node->getChild($targetKey);
				if (!is_object($targetNode) or get_class($targetNode)!='datanode') {
					// creating new node
					$targetNode=& new dataNode($targetKey);
					// attaching it on the tree
					$node->appendChild($targetNode);
					if ($this->verbose) echo "<p>growed node $targetKey on ".$node->getKey()."</p>";
				}
				$result[]=& $targetNode;
				return $result;
				break;
			default: // unsupportedf path
				trigger_error("DataTree->collectPath: unsupported path $path");
				return NULL;
		}
	}
/*--------------------------------------------------------------------------------------------------
																												syntax related
--------------------------------------------------------------------------------------------------*/
	// traduction en chemin slashes complet
	function fullSlashesPath($path,$syntax=NULL) {
		$oldpath=$path;
		if ($syntax!="slashes" and $syntax!="brackets") $syntax=$this->syntax;
		//if ($syntax=="slashes") $path=$this->slashesToBrackets($path);
		if ($syntax=="brackets") $path=$this->bracketsToSlashes($path);
		if ($path=='' or $path[0]!="/") $path=$this->context.'/'.$path;
		// suppression de slash final au besoin
		if (substr($path,-1,1)=='/') $path=substr($path,0,-1);
		if (!$this->checkSlashesSyntax($path)) {
			trigger_error("dataTree: failed to translate $path to full slashes syntax");
			return false;
		}
		//if ($this->verbose) echo "<p>translated $oldpath to $path</p>";
		return $path;
	}

	function slashesToBrackets($path) {
		// chemin contextuel
		if (!$path or $path[0]!='/') $path='~'.$path;
		else $path=substr($path,1);
		$steps=explode('/',$path);
		$newpath="";
		foreach ($steps as $step) {
			switch ($step) {
			case '~up': $newpath.="up"; break;
			case '~key': $newpath.="key"; break;
			case '~any':  $newpath.="any"; break;
			case '*':  $newpath.="*"; break;
			default: $newpath.="[$step]"; break;
			}
		}
		/*$path=str_replace('~up','',$path);
		$path=ereg_replace('([^/*~]+)','[\\1]',$path);
		$path=str_replace('/','',$path);*/
		return $newpath;
	}

	function bracketsToSlashes($path) {
		// chemin contextuel
		$oldpath=$path;
		if ($path=='' or $path[0]!='~') $path='/'.$path;
		else $path=substr($path,1);
		// up
		//$path=ereg_replace('([^[])?up([^]])?','\\1~up\\2',$path);
		$path=ereg_replace('\]up',']~up',$path);
		$path=ereg_replace('up\[','~up[',$path);
		// key
		//$path=ereg_replace('([^[])?key([^]])?','\\1~key\\2',$path);
		$path=ereg_replace('\]key',']~key',$path);
		$path=ereg_replace('key\[','~key[',$path);
		$path=ereg_replace('^key','~key',$path);
		// any
		$path=ereg_replace('([^[])?\*([^]])?','\\1~*\\2',$path);
		// *
		$path=ereg_replace('\[([^][]+)\]','\\1/',$path);
		if (substr($path,-1,1)=='/') $path=substr($path,0,-1);
		//echo $path.'<br />';
		// echo "<p>brackets $oldpath to slashes $path";
		return $path;
	}

	function checkSlashesSyntax($path) {
		// ajout slash initial ???
		if ($path=='' or $path[0]!='/') $path='/'.$path;
		//ereg de verif
		$ereg='^'.$this->makePathEreg('slashes').'$';// echo $ereg.':'.$path;
		if (ereg($ereg,$path)) return true;
		trigger_error("dataTree: invalid slash syntax $path , check ereg is $ereg",E_USER_NOTICE);
	}
}

class dataNode {
/*--------------------------------------------------------------------------------------------------
                                                                                         constructor
--------------------------------------------------------------------------------------------------*/
	function dataNode($key) {
		//$this->value=&$value;
		//$this->setvalue($value);
		if (!is_string($key) and !is_int($key)) {
			trigger_error("dataNode__constructor(key): key must be either string or integer",E_USER_ERROR);
		}
		$this->key=&$key;
		// $this->name=& $key
		// creation d'id globale et referencement
		if (!isset($GLOBALS['dataNodesLastId'])) $GLOBALS['dataNodesLastId']=0;
		$GLOBALS['dataNodesLastId']++;
		$this->id=$GLOBALS['dataNodesLastId'];
	}
/*--------------------------------------------------------------------------------------------------
                                                                                      public methods
--------------------------------------------------------------------------------------------------*/
	/*
	*/
	function setParent(&$parent) {
		$this->parent=&$parent;
	}

	function & getValue() {
		// echo "<p>getting dataNode #{$this->id} ({$this->key}) value.</p>";
		if ($this->childNodes===NULL)
			return $this->value;
		$values=array();
		$childKeys=array_keys($this->childNodes);
		foreach ($childKeys as $key) {
			$values[$key]=& $this->childNodes[$key]->getValue();
		}
		return $values;
	}

	function getAsText() {
		if ($this->childNodes===NULL)
			return strval($this->value);
		$txt='(';
		$childKeys=array_keys($this->childNodes);
		foreach ($childKeys as $key) {
			$txt.=' '.$key.'='.$this->childNodes[$key]->getAsText();
		}
		return $txt.')';
	}

	function setValue($value,$mergeNodes=true) {
		if (is_array($value)) {
			// creating empty children nodes array if necessary
			if (!is_array($this->childNodes) or !$mergeNodes)
				$this->childNodes=array();
			$keys=array_keys($value);
			foreach ($keys as $childkey) {
				$this->makeNewChild($value[$childkey],$childkey,$mergeNodes);
			}
		} else {
			$this->value=&$value;
			$this->childNodes=NULL;
		}
		//echo "<p>Set dataNode #{$this->id} ({$this->key}) to {$value}.</p>";
		return true;
	}

	function addValue($value) {
		$isLeaf=$this->isLeaf();
		switch (true) {
			case $isLeaf and is_string($this->value):
				if (is_array($value)) $value=join('',$value);
				if (!is_scalar($value)) return false;
				$this->value.=$value;
				return true;
				break;
			case !$isLeaf and is_array($value):
				$this->setValue($value,true);
				return true;
				break;
			case !$isLeaf:
				$key=$this->makeNewChild($value);
				return true;
				break;
			case $isLeaf and is_numeric($this->value):
				if (!is_numeric($value)) return false;
				$this->value+=$value;
				break;
			default:
				$this->value=$value;
				return false;
		}
	}

	/*function mergeValue($value) {
		if (is_string($value)
	}*/

	function appendChild(& $node, $forceOverWrite=false) {
		if (!is_object($node) or get_class($node)!='datanode') {
			trigger_error("dataNode->appendChild(& datanode node, [bool forceOverWrite]): not a datanode");
			return false;
		}
		$key=$node->getKey();
		if (isset($this->childNodes[$key]) and !$forceOverWrite) {
			trigger_error("dataNode->appendChild(& datanode node, [bool forceOverWrite]): a child exist yet with key $key, set forceOverWrite to true to replace");
			return false;
		}
		$this->childNodes[$key]=& $node;
		$node->setParent($this);
		return true;
	}

	function getKey() {
		return $this->key;
	}

	function getName() {
		return $this->getKey();
	}

	function & getChild($key) {
		if (isset($this->childNodes[$key]))
			return $this->childNodes[$key];
		$r=false;
		return $r;
	}

	function & getChildren() {
		return $this->childNodes;
	}

	function getChildrenNames() {
		return array_keys($this->childNodes);
	}

	function show() {
		echo "<pre>";
		print_r($this);
		echo "</pre>";
	}

	/* tells wether the node is a leaf (true) or a branch (false)
	*/
	function isLeaf() {
		return is_null($this->childNodes);
	}
	/* returns "length" of the node depending on context
	*/
	/*function weight() {
		switch (true) {
			case (is_array($this->childNodes)):
				return count($this->childNodes);
			case (is_string($this->value)):
				return strlen($this->value);
			case (is_numeric($this->value)):
				return strlen(abs($this->value));
			default:
				return NULL;
		}
	}*/

	function getType() {
		if ($this->isLeaf()) return "array";
		return getType($this->value);
	}

	function countLeaves() {
		if ($this->isLeaf()) return 1;
		$count=0; $keys=array_keys($this->childNodes);
		foreach ($keys as $key)
			$count+=$this->childNodes[$key]->countLeaves();
		return $count;
	}
/*--------------------------------------------------------------------------------------------------
                                                                                          properties
--------------------------------------------------------------------------------------------------*/
	var $parent; //ref to parent node
	// var $name;
	var $key; // = name of the node
	var $value; // NULL if node is a branch (has child nodes)
	var $childNodes; // children nodes array (indexed on node key) or NULL if leaf
	//var $type; // root|branch|leaf|loneleaf
/*--------------------------------------------------------------------------------------------------
                                                                                     private methods
--------------------------------------------------------------------------------------------------*/
	/*
	*/
	function makeChildKey() {
		$f=0; $max=50;
		while (array_key_exists($f,$this->childNodes)) {
			$f++;
			if ($f>$max) return false;
		}
		return $f;
	}/**/

	function makeNewChild($value,$key=NULL,$overWrite=true) {
		if ($key===NULL) $key=$this->makeChildKey();
		$node=&new dataNode($key);
		if (!$node->setValue($value)) return false;
		if (!$this->appendChild($node,$overWrite)) return false;
		return $key;
	}
}
?>
